package org.cable.link.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.corba.se.spi.ior.IdentifiableFactory;
import com.sun.org.apache.regexp.internal.RE;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.apache.bcel.generic.LineNumberGen;
import org.cable.common.constants.RedisKey;
import org.cable.common.enums.DomainTypeEnum;
import org.cable.common.enums.EventMessageType;
import org.cable.common.enums.ShortLinkStateEnum;
import org.cable.common.interceptor.LoginInterceptor;
import org.cable.common.model.EventMessage;
import org.cable.common.model.LoginUser;
import org.cable.common.util.CommonUtil;
import org.cable.common.util.IDUtil;
import org.cable.common.util.JsonData;
import org.cable.common.util.JsonUtil;
import org.cable.link.component.ShortLinkComponent;
import org.cable.link.config.RabbitMQConfig;
import org.cable.link.controller.request.ShortLinkAddRequest;
import org.cable.link.controller.request.ShortLinkDelRequest;
import org.cable.link.controller.request.ShortLinkPageRequest;
import org.cable.link.controller.request.ShortLinkUpdateRequest;
import org.cable.link.controller.request.UseTrafficRequest;
import org.cable.link.feign.TrafficFeignService;
import org.cable.link.manager.DomainManager;
import org.cable.link.manager.GroupCodeMappingManager;
import org.cable.link.manager.LinkGroupManager;
import org.cable.link.manager.ShortLinkManager;
import org.cable.link.mapper.ShortLinkMapper;
import org.cable.link.model.DomainDO;
import org.cable.link.model.GroupCodeMappingDO;
import org.cable.link.model.LinkGroupDO;
import org.cable.link.model.ShortLinkDO;
import org.cable.link.service.ShortLinkService;
import org.cable.link.vo.ShortLinkVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.awt.color.CMMException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author hsj
 * @since 2023-10-30
 */
@Service
@Slf4j
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {


    @Autowired
    private ShortLinkManager shortLinkManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private DomainManager domainManager;

    @Autowired
    private LinkGroupManager linkGroupManager;

    @Autowired
    private ShortLinkComponent shortLinkComponent;

    @Autowired
    private GroupCodeMappingManager groupCodeMappingManager;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private TrafficFeignService trafficFeignService;


    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {

        ShortLinkVO shortLinkVO = new ShortLinkVO();
        ShortLinkDO shortLinkDO = shortLinkManager.findByShortLinCode(shortLinkCode);
        if (null == shortLinkDO) {
            return null;
        }
        BeanUtils.copyProperties(shortLinkDO, shortLinkVO);
        return shortLinkVO;
    }


    @Override
    public JsonData createShortLink(ShortLinkAddRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

         //需要预先检查下是否有足够多的可以进行创建
        String cacheKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);

        //检查key是否存在,然后递减,是否大于0

        //如果key不存在,则未使用过,lua返回值是0，新增流量包的时候，不用重新计算次数,直接删除key

        /**保证同一个地址生成不同的code*/
        String newOriginal = CommonUtil.addUrlPrefix(request.getOriginalUrl());
        request.setOriginalUrl(newOriginal);
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    /**
     * 处理短链新增逻辑
     */
    @Override
    public boolean handleAddShortLink(EventMessage eventMessage) {

        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkAddRequest shortLinkAddRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);

        DomainDO domainDO = checkDomain(shortLinkAddRequest.getDomainType(), shortLinkAddRequest.getDomainId(), accountNo);
        LinkGroupDO linkGroupDO = checkLinkGroup(shortLinkAddRequest.getGroupId(), accountNo);

        String sign = CommonUtil.MD5(shortLinkAddRequest.getOriginalUrl());

        boolean duplicateCodeFlag = false;

        String shortLinkCode = shortLinkComponent.createShortLinkCode(shortLinkAddRequest.getOriginalUrl());

        //redis分布式锁
        //key1是短链码，ARGV[1]是accountNo,ARGV[2]是过期时间
        /**
         * 核心
         * * 先判断是否有，如没这个key，则设置key-value，配置过期时间，
         * 加锁成功
         * * 如果有这个key,判断value是否是同个账号，是同个账号则返回加锁
         * 成功
          * 如果不是同个账号则加锁失败
         */
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
            " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
            " else return 0; end;";

        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);

        //加锁成功
        if (result > 0) {
            if (EventMessageType.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(eventMessageType)) {
                /**判断是否短链码被占用*/
                ShortLinkDO shortLinkDODB = shortLinkManager.findByShortLinCode(shortLinkCode);
                if (null == shortLinkDODB) {

                    boolean reducedFlag = reduceTraffic(eventMessage,shortLinkCode);

                    if (reducedFlag){
                        /**c端处理*/
                        ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                                                             .accountNo(accountNo)
                                                             .code(shortLinkCode)
                                                             .title(shortLinkAddRequest.getTitle())
                                                             .originalUrl(shortLinkAddRequest.getOriginalUrl())
                                                             .domain(domainDO.getValue())
                                                             .groupId(linkGroupDO.getId())
                                                             .expired(shortLinkAddRequest.getExpired())
                                                             .sign(sign)
                                                             .state(ShortLinkStateEnum.ACTIVE.name())
                                                             .del(0)
                                                             .build();
                        shortLinkManager.addShortLink(shortLinkDO);
                        return true;
                    }
                } else {
                    log.error("c端短链码重复:{}", eventMessage);
                    duplicateCodeFlag = true;
                }
            } else if (EventMessageType.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
                GroupCodeMappingDO groupCodeMappingDODb = groupCodeMappingManager.findByCodeAndGroupId(shortLinkCode, linkGroupDO.getId(), accountNo);
                if (null == groupCodeMappingDODb) {
                    /**B端处理*/
                    GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(shortLinkAddRequest.getTitle())
                            .originalUrl(shortLinkAddRequest.getOriginalUrl())
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(shortLinkAddRequest.getExpired())
                            .sign(sign)
                            .state(ShortLinkStateEnum.ACTIVE.name())
                            .del(0)
                            .build();
                    groupCodeMappingManager.add(groupCodeMappingDO);
                    return true;
                } else {
                    log.error("B端短链码重复:{}", eventMessage);
                    duplicateCodeFlag = true;
                }
            }
        } else {
            //加锁失败,自旋100ms再调用 失败的可能短链码已经被占用了
            log.error("加锁失败：{}", eventMessage);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            duplicateCodeFlag = true;
        }


        if (duplicateCodeFlag) {
            String newOriginalUrl = CommonUtil.addUrlPrefixVersion(shortLinkAddRequest.getOriginalUrl());
            shortLinkAddRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setContent(JsonUtil.obj2Json(shortLinkAddRequest));
            log.warn("短链码报错失败,重新生成:{}", eventMessage);
            handleAddShortLink(eventMessage);
        }

        return false;
    }

    /**
     *扣减流量包逻辑
     * @param eventMessage
     * @param shortLinkCode
     * @return
     */
    private boolean reduceTraffic(EventMessage eventMessage, String shortLinkCode) {
        UseTrafficRequest request = UseTrafficRequest.builder().accountNo(eventMessage.getAccountNo())
            .bizId(shortLinkCode).build();

        JsonData jsonData = trafficFeignService.userTraffic(request);
        if (jsonData.getCode() != 0){
            log.error("流浪包不足,扣减失败：{}",eventMessage);
            return false;
        }
        return true;

    }

    @Override
    public boolean handleDelShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventMessageType();
        ShortLinkDelRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkDelRequest.class);

        if (EventMessageType.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(messageType)){
            /**C端处理*/
            ShortLinkDO shortLinkDO= ShortLinkDO.builder().code(request.getCode())
                    .build();

             int rows =  shortLinkManager.del(shortLinkDO,accountNo);
             log.info("删除C端：{}",rows);
             return true;

        }else if (EventMessageType.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(messageType)){
            /**B端处理*/
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(request.getMappingId())
                    .accountNo(accountNo)
                    .groupId(request.getGroupId()).build();
           int rows =  groupCodeMappingManager.del(groupCodeMappingDO);
            log.info("删除B端：{}",rows);
            return true;
        }


        return false;
    }

    @Override
    public boolean handleUpdateShortLink(EventMessage eventMessage) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        String messageType = eventMessage.getEventMessageType();
        ShortLinkUpdateRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkUpdateRequest.class);

        //校验短链域名
        DomainDO domainDO = checkDomain(request.getDomainType(), request.getDomainId(), accountNo);
        if (EventMessageType.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(messageType)) {
            /**C端处理*/
            ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                    .code(request.getCode())
                    .title(request.getTitle())
                    .domain(domainDO.getValue())
                    .build();

            int rows = shortLinkManager.update(shortLinkDO);
            log.info("更新C端短链,rows={}",rows);
            return rows >= 1 ? true : false;

        } else if (EventMessageType.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(messageType)) {
            /**B端处理*/
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().id(request.getMappingId())
                    .groupId(request.getGroupId())
                    .accountNo(accountNo)
                    .title(request.getTitle())
                    .domain(domainDO.getValue()).build();
            int rows = groupCodeMappingManager.update(groupCodeMappingDO);
            log.info("更新B端短链,rows={}",rows);
            return rows >= 1 ? true : false;
        }


        return false;
    }

    /**
     * 校验域名
     *
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {

        DomainDO domainDO;
        if (DomainTypeEnum.CUSTOM.name().equalsIgnoreCase(domainType)) {
            domainDO = domainManager.findById(domainId, accountNo);
        } else {
            domainDO = domainManager.findByDomainTypeAndID(domainId, DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO, "短链域名不合法");
        return domainDO;
    }


    /**
     * 校验组是否合法
     */

    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {
        LinkGroupDO linkGroupDO = linkGroupManager.detail(groupId, accountNo);
        Assert.notNull(linkGroupDO, "组名不合法!");
        return linkGroupDO;
    }

    /**
     * 从B端查找group_code_mapping表
     *
     * @param request
     * @return
     */
    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        Map<String, Object> map = groupCodeMappingManager.pageShortLinkByGroupId(request.getPage(), request.getSize(), accountNo, request.getGroupId());
        return map;
    }


    @Override
    public JsonData del(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_DEL.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinKDelRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }


    @Override
    public JsonData updateShortLink(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_UPDATE.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkUpdateRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }
}
